// (c) Copyright Fernando Cerqueira
// This source is subject to the Microsoft Public License (Ms-PL).
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
// All other rights reserved.

using System;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media.Animation;
using System.Windows.Threading;

// ReSharper disable CheckNamespace
namespace DevPhone.Controls
// ReSharper restore CheckNamespace
{
    public static class Zoom
    {
        private static DispatcherTimer _timer;
        private static Control _lastZoomControl;

        #region OriginalSize Property

        public static readonly DependencyProperty OriginalSizeProperty =
            DependencyProperty.RegisterAttached("OriginalSize", typeof(double), typeof(Control),
                                                new PropertyMetadata(double.NaN, null));

        private static double GetOriginalSize(DependencyObject dependencyObject)
        {
            if (dependencyObject == null) return double.NaN;
            return (double)dependencyObject.GetValue(OriginalSizeProperty);
        }

        private static void SetOriginalSize(DependencyObject dependencyObject, double value)
        {
            if (dependencyObject != null)
            {
                dependencyObject.SetValue(OriginalSizeProperty, value);
            }
        }

        #endregion

        #region Step Property

        public static readonly DependencyProperty StepProperty =
            DependencyProperty.RegisterAttached("Step", typeof(int?), typeof(Control),
                                                new PropertyMetadata(null, ZoomValueChangedCallback));

        public static int? GetStep(DependencyObject dependencyObject)
        {
            if (dependencyObject == null) return null;
            return (int?)dependencyObject.GetValue(StepProperty);
        }

        public static void SetStep(DependencyObject dependencyObject, int? value)
        {
            if (dependencyObject != null)
            {
                if (value == null)
                {
                    value = 0;
                }
                dependencyObject.SetValue(StepProperty, value);
            }
        }

        private static void ZoomValueChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
        {
            var instance = (Control)dependencyObject;
            if (e.NewValue == null && e.OldValue != null)
            {
                GestureService.GetGestureListener(instance).PinchDelta -= GestureListenerPinchDelta;
                instance.Loaded -= InstanceLoaded;
                instance.IsEnabledChanged -= InstanceIsEnabledChanged;
                instance.LostFocus -= InstanceLostFocus;
                ZoomChange(instance, 0);
            }
            else if (e.OldValue == null && e.NewValue != null)
            {
                GestureService.GetGestureListener(instance).PinchDelta += GestureListenerPinchDelta;
                instance.Loaded += InstanceLoaded;
                instance.IsEnabledChanged += InstanceIsEnabledChanged;
                instance.LostFocus += InstanceLostFocus;
            }
        }

        #endregion

        #region Host Loaded / IsEnabledChanged / Lostfocus

        private static void InstanceLostFocus(object sender, RoutedEventArgs e)
        {
            var instance = sender as Control;
            if (instance == null)
            {
                return;
            }
            var isolate = GetIsolateZoom(instance);

            if (!isolate)
            {
                if (_timer != null && _timer.IsEnabled)
                {
                    _timer.Stop();
                }
                var orisize = GetOriginalSize(_lastZoomControl);
                if (!double.IsNaN(orisize))
                {
                    var cz = _lastZoomControl.FontSize;
                    StoryBoardResetZoom(_lastZoomControl, cz, orisize);
                }
                _lastZoomControl = null;
            }
            else
            {
                var orisize = GetOriginalSize(instance);
                if (!double.IsNaN(orisize))
                {
                    var cz = instance.FontSize;
                    StoryBoardResetZoom(instance, cz, orisize);
                }
            }
        }

        private static void InstanceIsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            var instance = sender as Control;
            if (instance == null)
            {
                return;
            }
            if (!(bool)e.NewValue)
            {
                var isolate = GetIsolateZoom(instance);
                if (!isolate)
                {
                    if (_timer != null && _timer.IsEnabled)
                    {
                        _timer.Stop();
                    }
                    var orisize = GetOriginalSize(_lastZoomControl);
                    if (!double.IsNaN(orisize))
                    {
                        var cz = _lastZoomControl.FontSize;
                        StoryBoardResetZoom(_lastZoomControl, cz, orisize);
                    }
                    _lastZoomControl = null;
                }
                else
                {
                    var orisize = GetOriginalSize(instance);
                    if (!double.IsNaN(orisize))
                    {
                        var cz = instance.FontSize;
                        StoryBoardResetZoom(instance, cz, orisize);
                    }
                }
            }
        }

        private static void InstanceLoaded(object sender, RoutedEventArgs e)
        {
            var instance = (Control) sender;
            EnsureProperties(instance);
            var step = GetStep(instance);
            var tout = GetTimeout(instance);
            var isolate = GetIsolateZoom(instance);
            if (tout > 0 && step != null)
            {
                _timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(tout) };
                _timer.Tick += TimerTick;
                _timer.Start();
            }
            if (step != null)
            {
                if (!isolate)
                {
                    _lastZoomControl = instance;
                }
                ZoomChange(instance, step.Value);
            }
        }

        #endregion

        #region MaxStep Property

        public static readonly DependencyProperty MaxStepProperty =
            DependencyProperty.RegisterAttached("MaxStep", typeof(int), typeof(Zoom),
                                                new PropertyMetadata(int.Parse("35", CultureInfo.InvariantCulture), null));

        public static int GetMaxStep(DependencyObject dependencyObject)
        {
            if (dependencyObject == null) return 0;
            return (int)dependencyObject.GetValue(MaxStepProperty);
        }

        public static void SetMaxStep(DependencyObject dependencyObject, int value)
        {
            if (dependencyObject != null)
            {
                if (value < 1)
                {
                    value = 1;
                }
                dependencyObject.SetValue(MaxStepProperty, value);
            }
        }

        #endregion

        #region MaxSize Property

        public static readonly DependencyProperty MaxSizeProperty =
            DependencyProperty.RegisterAttached("MaxSize", typeof(double), typeof(Zoom),
                                                new PropertyMetadata(double.Parse("0", CultureInfo.InvariantCulture), null));

        public static double GetMaxSize(DependencyObject dependencyObject)
        {
            if (dependencyObject == null) return 0;
            return (double)dependencyObject.GetValue(MaxSizeProperty);
        }

        public static void SetMaxSize(DependencyObject dependencyObject, double value)
        {
            if (dependencyObject != null)
            {
                dependencyObject.SetValue(MaxSizeProperty, value);
            }
        }

        #endregion

        #region Timeout Property

        public static readonly DependencyProperty TimeoutProperty =
            DependencyProperty.RegisterAttached("Timeout", typeof(double), typeof(Zoom),
                                                new PropertyMetadata(double.Parse("0", CultureInfo.InvariantCulture), null));

        public static double GetTimeout(DependencyObject dependencyObject)
        {
            if (dependencyObject == null) return 0;
            return (double)dependencyObject.GetValue(TimeoutProperty);
        }

        public static void SetTimeout(DependencyObject dependencyObject, double value)
        {
            if (dependencyObject != null)
            {
                dependencyObject.SetValue(TimeoutProperty, value);
            }
        }

        #endregion

        #region ManualZoom Property

        public static readonly DependencyProperty IsolateZoomProperty =
            DependencyProperty.RegisterAttached("IsolateZoom", typeof(bool), typeof(Zoom),
                                        new PropertyMetadata(false, null));

        public static bool GetIsolateZoom(DependencyObject dependencyObject)
        {
            if (dependencyObject == null) return false;
            return (bool)dependencyObject.GetValue(IsolateZoomProperty);
        }

        public static void SetIsolateZoom(DependencyObject dependencyObject, bool value)
        {
            if (dependencyObject != null)
            {
                dependencyObject.SetValue(IsolateZoomProperty, value);
            }
        }

        #endregion

        #region private Methods

        private static void GestureListenerPinchDelta(object sender, PinchGestureEventArgs e)
        {
            if (e.DistanceRatio.Equals(1.0))
            {
                return;
            }
            ZoomHost((Control)sender, e.DistanceRatio > 1.0);
        }

        private static void ZoomHost(DependencyObject sender, bool zoomOut)
        {

            var zvalue = GetStep(sender);
            var isolate = GetIsolateZoom(sender);

            if (!zvalue.HasValue) return;

            var instance = sender as Control;

            if (instance == null)
            {
                return;
            }

            if (_lastZoomControl == null && !isolate)
            {
                _lastZoomControl = instance;
                var tout = GetTimeout(sender);
                if (tout > 0)
                {
                    _timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(tout) };
                    _timer.Tick += TimerTick;
                    _timer.Start();
                }
            }
            else if (!isolate && _lastZoomControl != instance)
            {
                if (_timer != null && _timer.IsEnabled)
                {
                    _timer.Stop();
                }
                var orisize = GetOriginalSize(_lastZoomControl);
                if (!double.IsNaN(orisize))
                {
                    var cz = _lastZoomControl.FontSize;
                    StoryBoardResetZoom(_lastZoomControl, cz, orisize);
                    _lastZoomControl = null;
                }
                _lastZoomControl = instance;
                var tout = GetTimeout(sender);
                if (tout > 0)
                {
                    _timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(tout) };
                    _timer.Tick += TimerTick;
                    _timer.Start();
                }
            }
            else if (!isolate && _lastZoomControl.Equals(instance))
            {
                var tout = GetTimeout(sender);
                if (tout > 0)
                {
                    _timer.Stop();
                    _timer.Start();
                }
            }


            if (zoomOut && zvalue.Value + 1 <= GetMaxStep(sender))
            {
                ZoomChange(sender, zvalue.Value + 1);
            }
            if (!zoomOut && zvalue.Value - 1 >= 0)
            {
                ZoomChange(sender, zvalue.Value - 1);
            }
        }

        private static void TimerTick(object sender, EventArgs e)
        {
            var orisize = GetOriginalSize(_lastZoomControl);
            if (!double.IsNaN(orisize))
            {
                var cz = _lastZoomControl.FontSize;
                StoryBoardResetZoom(_lastZoomControl, cz, orisize);
                _lastZoomControl = null;
            }
            _timer.Stop();
        }

        private static void StoryBoardResetZoom(Control ctrl, double currentSize, double oriSize)
        {
            var sb = new Storyboard();
            var da = new DoubleAnimation { Duration = new Duration(TimeSpan.FromMilliseconds(400)), From = currentSize, To = oriSize };
            Storyboard.SetTarget(da, ctrl);
            Storyboard.SetTargetProperty(da, new PropertyPath("(UIElement.FontSize)"));
            sb.Children.Add(da);
            sb.Begin();
        }

        private static void ZoomChange(DependencyObject sender, int step)
        {
            var oriSize = GetOriginalSize(sender);
            var maxSize = GetMaxSize(sender);
            var maxStep = GetMaxStep(sender);

            var tick = (maxSize - oriSize) / maxStep;

            var newfnt = GetOriginalSize(sender) + (tick * step);
            if (newfnt > maxSize)
            {
                newfnt = maxSize;
                SetStep(sender, maxStep);
            }
            else
            {
                SetStep(sender, step);
            }
            ((Control)sender).FontSize = newfnt;
        }

        private static void EnsureProperties(DependencyObject sender)
        {
            var maxSize = GetMaxSize(sender);
            var oriSize = GetOriginalSize(sender);
            var maxStep = GetMaxStep(sender);
            var zvalue = GetStep(sender);
            var instance = sender as Control;
            if (instance == null || zvalue == null)
            {
                return;
            }

            if (zvalue < 0)
            {
                zvalue = 0;
                SetStep(sender, zvalue.Value);
            }
            if (zvalue > maxStep)
            {
                zvalue = maxStep;
                SetStep(sender, zvalue.Value);
            }

            if (double.IsNaN(oriSize))
            {
                oriSize = instance.FontSize;
                SetOriginalSize(sender, oriSize);
                if (maxSize <= oriSize && !maxSize.Equals(0))
                {
                    SetMaxSize(sender, instance.FontSize);
                }
            }
            if (maxSize.Equals(0))
            {
                SetMaxSize(sender, instance.FontSize * 4);
            }
        }

        #endregion

    }
}